package net.chrisrichardson.ec2deployer.core;

import groovy.mock.interceptor.MockFor
import org.apache.commons.logging.*
import net.chrisrichardson.ec2deployer.cluster.EbsVolume

class EC2Test extends GroovyTestCase {

    Log logger = LogFactory.getLog(getClass())
    EC2 ec2
    AWSProperties awsProperties
    MockFor mockEC2RequestExecutor
    MockFor mockEC2Server

    public static void assertEquals(expected, actual) {
        if (!expected.equals(actual)) {
            fail "Expected <$expected> but got <$actual>"
        }
    }

    def RunInstancesResponse = """<RunInstancesResponse xmlns="http://ec2.amazonaws.com/doc/2007-01-03/">
<reservationId>r-60907709</reservationId>
<ownerId>556666664445</ownerId>
<groupSet>
    <item>
        <groupId>default</groupId>
    </item>
</groupSet>
<instancesSet>
    <item>
        <instanceId>i-4ef21327</instanceId>
        <imageId>ami-3795705e</imageId>
        <instanceState>
            <code>0</code>
            <name>pending</name>
        </instanceState>
        <placement>
          <availabilityZone>us-east-1b</availabilityZone>
        </placement>
        <dnsName/>
        <reason/>
        <keyName>gsg-keypair</keyName>
        <amiLaunchIndex>0</amiLaunchIndex>
    </item>
</instancesSet>
</RunInstancesResponse>
"""
    def describeInstancesResponse = """
<DescribeInstancesResponse xmlns="http://ec2.amazonaws.com/doc/2007-01-19/">
    <reservationSet>
        <item>
            <reservationId>r-635bb30a</reservationId>
            <ownerId>556666664445</ownerId>
            <groupSet>
                <item>
                    <groupId>default</groupId>
                </item>
            </groupSet>
            <instancesSet>
                <item>
                    <instanceId>i-4b28d922</instanceId>
                    <imageId>ami-422dc82b</imageId>
                    <instanceState>
                        <code>16</code>
                        <name>running</name>
                    </instanceState>
                    <placement>
                        <availabilityZone>us-east-1b</availabilityZone>
                    </placement>     
                    <privateDnsName>domU-12-31-38-00-42-36.compute-1.internal</privateDnsName>
                    <dnsName>ec2-67-202-20-108.compute-1.amazonaws.com</dnsName>
                    <reason/>
                    <keyName>gsg-keypair</keyName>
                    <amiLaunchIndex>0</amiLaunchIndex>
                </item>
            </instancesSet>
        </item>
    </reservationSet>
</DescribeInstancesResponse>
"""

    def describeAvailabiltyZonesResponse = """
<DescribeAvailabilityZonesResponse xmlns="http://ec2.amazonaws.com/doc/2008-02-01/">
    <availabilityZoneInfo>
        <item>
            <zoneName>us-east-1a</zoneName>
            <zoneState>available</zoneState>
        </item>
        <item>
            <zoneName>us-east-1b</zoneName>
            <zoneState>available</zoneState>
        </item>
        <item>
            <zoneName>us-east-1c</zoneName>
            <zoneState>unavailable</zoneState>
        </item>
    </availabilityZoneInfo>
</DescribeAvailabilityZonesResponse>
"""

    public void setUp() {
        def props = new Properties()
        awsProperties = new AWSProperties(props)
        props["imageId.m1.small"] = "ami-small"
        props["keyName"] = "gsg-keypair"
        props["accountId"] = "123456"
        props["imageId.ami-small"] = "ami-1245"
        mockEC2RequestExecutor = new MockFor(EC2RequestExecutor.class)
        mockEC2Server = new MockFor(EC2Server)
    }

    def configureRequestExecutorToCreateOneServer() {
        mockEC2RequestExecutor.demand.executeRequest {params ->
            assertEquals(['Action': 'RunInstances', 'MinCount': "1", 'MaxCount': "1", 'ImageId': "ami-small", 'KeyName': 'gsg-keypair', "InstanceType": "m1.small"], params)
            def parser = new XmlParser(false, false)
            return parser.parse(new StringReader(runInstancesResponse))
        }
    }

    void testNewServers() {
        configureRequestExecutorToCreateOneServer()
        mockEC2RequestExecutor.use {
            ec2 = new EC2(awsProperties)
            def servers = ec2.newServers(1)

            assertEquals 1, servers.size()
            assertEquals ec2.servers, servers

            EC2Server server = servers[0];

            assertEquals "i-4ef21327", server.instanceId
            assertEquals "pending", server.state
            assertEquals "us-east-1b", server.instanceState.availabilityZone
        }
    }

    String describeAvailabilityZonesResponse = """<DescribeAvailabilityZonesResponse
xmlns="http://ec2.amazonaws.com/doc/2008-02-01/">
  <availabilityZoneInfo>
    <item>
      <zoneName>us-east-1a</zoneName>
      <zoneState>available</zoneState>
    </item>
    <item>
      <zoneName>us-east-1b</zoneName>
      <zoneState>available</zoneState>
    </item>
    <item>
      <zoneName>us-east-1c</zoneName>
      <zoneState>available</zoneState>
    </item>
  </availabilityZoneInfo>
</DescribeAvailabilityZonesResponse>
"""

    void testNewServersElsewhere() {
        mockEC2RequestExecutor.demand.executeRequest {params ->
            assertEquals(["Action":"DescribeAvailabilityZones"], params)
            def parser = new XmlParser(false, false)
            return parser.parse(new StringReader(describeAvailabilityZonesResponse))
        }

        mockEC2Server.demand.getAvailabilityZone { "us-east-1a" }

        mockEC2RequestExecutor.demand.executeRequest {params ->
            assertEquals(["Action":"RunInstances", "ImageId":"ami-1245", "InstanceType":"ami-small", "KeyName":"gsg-keypair", "MaxCount":"1", "MinCount":"1", "Placement.AvailabilityZone":"us-east-1b"], params)
            def parser = new XmlParser(false, false)
            return parser.parse(new StringReader(runInstancesResponse))
        }
        mockEC2Server.use {
            mockEC2RequestExecutor.use {
                ec2 = new EC2(awsProperties)
                def otherServer = new EC2Server()
                def servers = ec2.newServersElsewhere(1, "ami-small", otherServer, [:])

                assertEquals 1, servers.size()
                assertEquals ec2.servers, servers

            }
        }
    }

    void testReserve_needOne() {
        configureRequestExecutorToCreateOneServer()
        mockEC2RequestExecutor.use {
            ec2 = new EC2(awsProperties)
            def servers = ec2.reserve(1)

            assertEquals 1, servers.size()
            assertEquals servers, ec2.servers
            assertEquals servers, ec2.reserved
        }
    }

    void testRelease() {
        configureRequestExecutorToCreateOneServer()
        mockEC2RequestExecutor.use {
            ec2 = new EC2(awsProperties)
            def servers = ec2.reserve(1)

            ec2.release(servers)
            assertTrue ec2.reserved.isEmpty()
        }
    }

    void testReserveHasOne() {
        configureRequestExecutorToCreateOneServer()
        mockEC2RequestExecutor.use {
            ec2 = new EC2(awsProperties)
            def servers = ec2.reserve(1)
            ec2.release(servers)

            def servers2 = ec2.reserve(1)
            assertEquals servers2, ec2.reserved
        }
    }

    void testStop() {
        configureRequestExecutorToCreateOneServer()
        mockEC2RequestExecutor.demand.executeRequest {params ->
            assertEquals(["Action": "TerminateInstances", "InstanceId.1": "i-4ef21327"], params)
        }
        mockEC2RequestExecutor.use {
            ec2 = new EC2(awsProperties)
            def servers = ec2.reserve(1)
            EC2Server server = servers[0];

            ec2.stop(server)
            assertTrue ec2.servers.isEmpty()
            assertTrue ec2.reserved.isEmpty()
        }
    }

    void testPollStatus_discoveringNewServer() {
        mockEC2RequestExecutor.demand.executeRequest {params ->
            logger.debug(params)
            assertEquals(["Action": "DescribeInstances"], params)
            def parser = new XmlParser(false, false)
            return parser.parse(new StringReader(describeInstancesResponse))
        }
        def mockEC2Server = new MockFor(EC2Server.class)
        mockEC2Server.demand.updateStatus {state, publicDnsName, privateDnsName ->
            assertEquals "running", state
            assertEquals "ec2-67-202-20-108.compute-1.amazonaws.com", publicDnsName
            assertEquals "domU-12-31-38-00-42-36.compute-1.internal", privateDnsName
        }
        mockEC2Server.use {
            mockEC2RequestExecutor.use {
                ec2 = new EC2(awsProperties)
                ec2.pollStatus()
                assertEquals 1, ec2.servers.size()
            }
        }
    }

    void testPollStatus_existingNewServer() {
        mockEC2RequestExecutor.demand.executeRequest {params ->
            assertEquals(["Action": "DescribeInstances"], params)
            def parser = new XmlParser(false, false)
            return parser.parse(new StringReader(describeInstancesResponse))
        }
        mockEC2RequestExecutor.demand.executeRequest {params ->
            assertEquals(["Action": "DescribeInstances"], params)
            def parser = new XmlParser(false, false)
            return parser.parse(new StringReader(describeInstancesResponse))
        }
        def mockEC2Server = new MockFor(EC2Server.class)
        mockEC2Server.demand.updateStatus {state, publicDnsName, privateDnsName ->
            assertEquals "running", state
            assertEquals "ec2-67-202-20-108.compute-1.amazonaws.com", publicDnsName
            assertEquals "domU-12-31-38-00-42-36.compute-1.internal", privateDnsName
        }
        mockEC2Server.demand.getInstanceId {"i-4b28d922"}
        mockEC2Server.demand.updateStatus {state, publicDnsName, privateDnsName ->
            assertEquals "running", state
            assertEquals "ec2-67-202-20-108.compute-1.amazonaws.com", publicDnsName
            assertEquals "domU-12-31-38-00-42-36.compute-1.internal", privateDnsName
        }

        mockEC2Server.use {
            mockEC2RequestExecutor.use {
                ec2 = new EC2(awsProperties)
                ec2.pollStatus()
                assertEquals 1, ec2.servers.size()
                ec2.pollStatus()
                assertEquals 1, ec2.servers.size()
            }
        }
    }

    void testDescribeAvailabilityZones() {
        mockEC2RequestExecutor.demand.executeRequest {params ->
            logger.debug(params)
            assertEquals(["Action": "DescribeAvailabilityZones"], params)
            def parser = new XmlParser(false, false)
            return parser.parse(new StringReader(describeAvailabiltyZonesResponse))
        }
        mockEC2RequestExecutor.use {
            ec2 = new EC2(awsProperties)
            ec2.fetchAvailabilityZonesIfNecessary()
            assertEquals(["us-east-1a", "us-east-1b"], ec2.availabilityZones)
        }
    }
    
    def associationAddressResponse = """<AssociateAddressResponse xmlns="http://ec2.amazonaws.com/doc/2008-02-01">
  <return>true</return>
</AssociateAddressResponse>"""

    void testAssociateAddress() {
      mockEC2RequestExecutor.demand.executeRequest {params ->
        assertEquals(['Action': 'AssociateAddress', 'InstanceId': "i-124", 'PublicIp': "192.168.1.100"], params)
        def parser = new XmlParser(false, false)
        return parser.parse(new StringReader(associationAddressResponse))
      }
      mockEC2RequestExecutor.use {
        ec2 = new EC2(awsProperties)
        def ec2Server = new EC2Server(ec2, "i-124", "ami-4545", "running", "oakland")
        assert (ec2.associateAddress(ec2Server, "192.168.1.100"))
    }
     
    }

    String describeSecurityGroupsResponse = """
       <DescribeSecurityGroupsResponse xmlns="http://ec2.amazonaws.com/doc/2008-02-01/">
        <securityGroupInfo>
            <item>
                <ownerId>94545949849584</ownerId>
                <groupName>testgroups-webServers</groupName>
                <groupDescription>testgroups-webServersdescription</groupDescription>
                <ipPermissions>
                    <item>
                        <ipProtocol>tcp</ipProtocol>
                        <fromPort>22</fromPort>
                        <toPort>22</toPort>
                        <groups/>
                        <ipRanges>
                            <item>
                                <cidrIp>0.0.0.0/0</cidrIp>
                            </item>
                        </ipRanges>
                    </item>
                    <item>
                        <ipProtocol>tcp</ipProtocol>
                        <fromPort>22</fromPort>
                        <toPort>22</toPort>
                        <groups>
                            <group>
                                <groupName>otherGroup</groupName>
                                <userId>123456</userId>
                            </group>
                        </groups>
                        <ipRanges/>
                    </item>
                </ipPermissions>
            </item>
        </securityGroupInfo>
    </DescribeSecurityGroupsResponse>
    """

    public void testConfigureSecurityGroup_new() {
        mockEC2RequestExecutor.demand.executeRequest {params ->
            assertEquals(["Action": "DescribeSecurityGroups"], params)
            def parser = new XmlParser(false, false)
            return parser.parse(new StringReader(describeSecurityGroupsResponse))

        }
        mockEC2RequestExecutor.demand.executeRequest {params ->
            assertEquals(["Action": "CreateSecurityGroup", "GroupName": "myNewGroup", "GroupDescription": "myNewGroupdescription"], params)
            null

        }
        mockEC2RequestExecutor.demand.executeRequest {params ->
            assertEquals(["Action": "AuthorizeSecurityGroupIngress", "GroupName": "myNewGroup", "IpProtocol": "tcp", "FromPort": "22", "ToPort": "22", "CidrIp": "0.0.0.0/0"], params)
            null

        }
        mockEC2RequestExecutor.demand.executeRequest {params ->
            assertEquals(["Action": "AuthorizeSecurityGroupIngress", "GroupName": "myNewGroup", "SourceSecurityGroupName": "otherGroup", "SourceSecurityGroupOwnerId": "123456"], params)
            null

        }
        mockEC2RequestExecutor.use {
            ec2 = new EC2(awsProperties)
            ec2.configureSecurityGroup "myNewGroup", ["otherGroup": 3306]
        }
    }

    public void testConfigureSecurityGroup_existing_with_perms() {
        mockEC2RequestExecutor.demand.executeRequest {params ->
            assertEquals(["Action": "DescribeSecurityGroups"], params)
            def parser = new XmlParser(false, false)
            return parser.parse(new StringReader(describeSecurityGroupsResponse))

        }
        mockEC2RequestExecutor.use {
            ec2 = new EC2(awsProperties)
            ec2.configureSecurityGroup "testgroups-webServers", ["otherGroup": 3306]
        }

    }

    def describeVolumesResponse = """<DescribeVolumesResponse xmlns="http://ec2.amazonaws.com/doc/2008-05-05">
<volumeSet>
  <item>
<volumeId>vol-4282672b</volumeId>
<size>800</size>
<status>in-use</status>
<createTime>2008-05-07T11:51:50.000Z</createTime>
<availabilityZone>hong-kong</availabilityZone>
  </item>
</volumeSet></DescribeVolumesResponse>"""

    public void testGetAvailabilityZoneOfEbsVolume() {

        mockEC2RequestExecutor.demand.executeRequest {params ->
            assertEquals(["Action":"DescribeVolumes", "VolumeId.1":"vol-1234"], params)
            def parser = new XmlParser(false, false)
            return parser.parse(new StringReader(describeVolumesResponse))

        }

        mockEC2RequestExecutor.use {
            ec2 = new EC2(awsProperties)
            String result = ec2.getAvailabilityZoneOfEbsVolume(EbsVolume.existingVolume("vol-1234", "/dev/sdq"))
            assertEquals  "hong-kong", result

        }

    }

    public void testGetStatusOfEbsVolume() {

        mockEC2RequestExecutor.demand.executeRequest {params ->
            assertEquals(["Action":"DescribeVolumes", "VolumeId.1":"vol-1234"], params)
            def parser = new XmlParser(false, false)
            return parser.parse(new StringReader(describeVolumesResponse))

        }

        mockEC2RequestExecutor.use {
            ec2 = new EC2(awsProperties)
            String result = ec2.getStatusOfEbsVolume(EbsVolume.existingVolume("vol-1234", "/dev/sdq"))
            assertEquals  "in-use", result

        }

    }
}
